import sys
import os
import threading
import queue
import serial
import serial.tools.list_ports
import crcmod
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from tkinter.scrolledtext import ScrolledText
from datetime import datetime

# 系统配置常量
COM_PORTS = [f"COM{i}" for i in range(1, 9)]  # 模拟8个COM口
BAUDRATE = 115200
PACKET_SIZE = 512
CRC16_FUNC = crcmod.mkCrcFun(0x11021, rev=False, initCrc=0xFFFF)


class SerialChannel:
    """串口通道管理类"""

    def __init__(self, port, baudrate):
        self.port = port
        self.baudrate = baudrate
        self.ser = None
        self.rx_queue = queue.Queue()
        self.tx_queue = queue.Queue()
        self.running = False
        self.progress = 0
        self.status = "Ready"
        self.error_code = 0

    def open(self):
        """打开串口连接"""
        try:
            self.ser = serial.Serial(
                port=self.port,
                baudrate=self.baudrate,
                bytesize=serial.EIGHTBITS,
                parity=serial.PARITY_NONE,
                stopbits=serial.STOPBITS_ONE,
                timeout=0.1
            )
            self.running = True
            threading.Thread(target=self._rx_thread, daemon=True).start()
            threading.Thread(target=self._tx_thread, daemon=True).start()
            return True
        except serial.SerialException as e:
            self.status = f"Error: {str(e)}"
            return False

    def close(self):
        """关闭串口连接"""
        self.running = False
        if self.ser and self.ser.is_open:
            self.ser.close()

    def _rx_thread(self):
        """数据接收线程"""
        while self.running:
            try:
                if self.ser.in_waiting > 0:
                    data = self.ser.read(self.ser.in_waiting)
                    self.rx_queue.put(data)
            except Exception as e:
                self.status = f"RX Error: {str(e)}"
                break

    def _tx_thread(self):
        """数据发送线程"""
        while self.running:
            try:
                if not self.tx_queue.empty():
                    data = self.tx_queue.get()
                    self.ser.write(data)
            except Exception as e:
                self.status = f"TX Error: {str(e)}"
                break


class BurnManager:
    """烧录管理核心类"""

    def __init__(self):
        self.channels = {}
        self.file_data = b''
        self.current_channel = 0
        self._init_channels()

    def _init_channels(self):
        """初始化所有通道"""
        for port in COM_PORTS:
            self.channels[port] = SerialChannel(port, BAUDRATE)

    def open_channel(self, port):
        """打开指定通道"""
        return self.channels[port].open()

    def start_burn(self, port, file_path):
        """启动烧录流程"""

        def burn_task():
            try:
                # 加载文件数据
                with open(file_path, 'rb') as f:
                    self.file_data = f.read()

                # 分包发送
                total = len(self.file_data)
                for i in range(0, total, PACKET_SIZE):
                    packet = self._build_packet(i, self.file_data[i:i + PACKET_SIZE])
                    self.channels[port].tx_queue.put(packet)

                    # 更新进度
                    progress = (i + PACKET_SIZE) / total * 100
                    self.channels[port].progress = min(progress, 100)

                    # 简单模拟接收确认
                    if not self._wait_ack(port):
                        raise TimeoutError("Ack timeout")

                self.channels[port].status = "Completed"
            except Exception as e:
                self.channels[port].status = f"Error: {str(e)}"

        threading.Thread(target=burn_task, daemon=True).start()

    def _build_packet(self, seq, data):
        """构建数据包（包含CRC校验）"""
        header = seq.to_bytes(4, 'big')
        crc = CRC16_FUNC(data).to_bytes(2, 'big')
        return header + data + crc

    def _wait_ack(self, port, timeout=1):
        """等待设备确认"""
        start = datetime.now()
        while (datetime.now() - start).total_seconds() < timeout:
            if not self.channels[port].rx_queue.empty():
                ack = self.channels[port].rx_queue.get()
                if ack == b'\x06':  # ASCII ACK
                    return True
        return False


class BurnerApp(tk.Tk):
    """主界面类"""

    def __init__(self):
        super().__init__()
        self.title("烧录工具")
        self.geometry("1200x800")
        self.burn_mgr = BurnManager()

        # 初始化界面
        self._setup_ui()
        self._update_ui()

    def _setup_ui(self):
        """构建用户界面"""
        main_frame = ttk.Frame(self, padding=10)
        main_frame.pack(fill=tk.BOTH, expand=True)

        # 控制面板
        control_frame = ttk.LabelFrame(main_frame, text="控制面板", padding=10)
        control_frame.pack(fill=tk.X, pady=5)

        # 通道选择
        ttk.Label(control_frame, text="选择通道:").grid(row=0, column=0)
        self.port_combo = ttk.Combobox(control_frame, values=COM_PORTS)
        self.port_combo.current(0)
        self.port_combo.grid(row=0, column=1, padx=5)

        # 文件选择
        self.file_entry = ttk.Entry(control_frame, width=40)
        self.file_entry.grid(row=0, column=2, padx=5)
        ttk.Button(control_frame, text="浏览...", command=self._select_file).grid(row=0, column=3)

        # 操作按钮
        btn_frame = ttk.Frame(control_frame)
        btn_frame.grid(row=0, column=4, padx=10)
        ttk.Button(btn_frame, text="打开通道", command=self._open_port).pack(side=tk.LEFT)
        ttk.Button(btn_frame, text="开始烧录", command=self._start_burn).pack(side=tk.LEFT)
        ttk.Button(btn_frame, text="停止", command=self._stop_burn).pack(side=tk.LEFT)

        # 状态面板
        status_frame = ttk.LabelFrame(main_frame, text="通道状态", padding=10)
        status_frame.pack(fill=tk.BOTH, expand=True)

        # 状态表格
        self.status_tree = ttk.Treeview(status_frame, columns=('port', 'status', 'progress', 'error'))
        self.status_tree.heading('#0', text='Channel')
        self.status_tree.heading('port', text='Port')
        self.status_tree.heading('status', text='Status')
        self.status_tree.heading('progress', text='Progress')
        self.status_tree.heading('error', text='Error Code')
        self.status_tree.pack(fill=tk.BOTH, expand=True)

        # 日志面板
        log_frame = ttk.LabelFrame(main_frame, text="系统日志", padding=10)
        log_frame.pack(fill=tk.BOTH, expand=True)
        self.log_text = ScrolledText(log_frame, wrap=tk.WORD)
        self.log_text.pack(fill=tk.BOTH, expand=True)

    def _select_file(self):
        """选择烧录文件"""
        path = filedialog.askopenfilename(filetypes=[("Binary Files", "*.bin")])
        if path:
            self.file_entry.delete(0, tk.END)
            self.file_entry.insert(0, path)

    def _open_port(self):
        """打开选定通道"""
        port = self.port_combo.get()
        if self.burn_mgr.open_channel(port):
            self._log(f"Port {port} opened")
        else:
            messagebox.showerror("Error", f"Failed to open {port}")

    def _start_burn(self):
        """启动烧录流程"""
        port = self.port_combo.get()
        file_path = self.file_entry.get()

        if not file_path:
            messagebox.showwarning("Warning", "Please select a file first")
            return

        self.burn_mgr.start_burn(port, file_path)
        self._log(f"Burn started on {port}")

    def _stop_burn(self):
        """停止烧录操作"""
        port = self.port_combo.get()
        self.burn_mgr.channels[port].close()
        self._log(f"Burn stopped on {port}")

    def _update_ui(self):
        """更新界面状态"""
        for port in COM_PORTS:
            channel = self.burn_mgr.channels[port]
            values = (port, channel.status, f"{channel.progress:.1f}%", channel.error_code)
            if not self.status_tree.exists(port):
                self.status_tree.insert('', 'end', iid=port, values=values)
            else:
                self.status_tree.item(port, values=values)
        self.after(500, self._update_ui)

    def _log(self, message):
        """记录日志"""
        timestamp = datetime.now().strftime("%H:%M:%S")
        self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
        self.log_text.see(tk.END)


if __name__ == "__main__":
    app = BurnerApp()
    app.mainloop()